﻿using AiMoBridge.Interface;
using AiMoBridge.Interface.Client;
using AiMoWindows.CodeScripts.Analysis;
using AiMoWindows.CodeScripts.IO;
using AiMoWindows.Compiler;
using AiMoWindows.Entrance;
using AiMoWindows.Localization;
using AiMoWindows.MainActions;
using AiMoWindows.MainActions.Mode;
using AiMoWindows.Server;
using AiMoWindows.Windows.SaveCode;
using Microsoft.CodeAnalysis;
using Microsoft.Win32;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;

namespace AiMoWindows.CodeScripts
{
    /// <summary>
    /// 编写这个类，就是为了隔离Window和Form与Scripts的操作。前端所有操作，都通过这个类来进行
    /// 包括：
    ///     获取参考Assemblies
    /// </summary>
    class ScriptManager(IExecutor? executeScripts, ScriptSource scriptSource, string filePath)
    {

        public ScriptSource Script { get; private set; } = scriptSource;
        string savePath = filePath;
        IExecutor? RhinoExecutor { get; } = executeScripts;
        string CodeSaveHash { get; set; } = "";

        public delegate void StringEventHandler(string text);
        public event StringEventHandler? OnSavePathChanged;
        public string SavePath
        {
            get => savePath;
            private set
            {
                if (savePath != value)
                {
                    savePath = value;
                    OnSavePathChanged?.Invoke(value);
                }
            }
        }
        public bool HasSavePath => !string.IsNullOrEmpty(savePath);
        public bool NotSave => Script.GetScriptHash() != CodeSaveHash;
        public ScriptComplileMode CompileMode => Script.ScriptsMode;
        public delegate void CompileModeChangeEventHandler(ScriptComplileMode mode);
        public event CompileModeChangeEventHandler? CompileModeChanged;

        public void SetSaveCodeHash()
        {
            CodeSaveHash = Script.GetScriptHash();
        }

        public void ResetToNew()
        {
            Script = new ScriptSource(Script.ScriptsMode, null, true);
            SavePath = "";
            SetSaveCodeHash();
        }

        #region Append Code

        public void AppendOnLoadCode()
        {
            string[] currentFunctionLines = Script.AdditionalFunctionCode.Lines;

            foreach (string line in currentFunctionLines)
            {
                if (line.Trim().StartsWith("//")) continue;
                if (line.Contains("override void OnLoad()"))
                {
                    return;
                }
            }
            List<string> linesFunctions = new List<string>()
            {
                "",
                "  //当载入代码时",
                "  public override void OnLoad()",
                "  {",
                "    //在运行按钮点击后，Rhino会先载入代码，然后运行Main函数。",
                "    //当载入时运行这个函数，只会运行一次。可以用于添加Event等",
                "  }",
                "",
            };
            linesFunctions.AddRange(currentFunctionLines);
            Script.AdditionalFunctionCode.UpdateLines(linesFunctions);
        }

        public void AppendLoopCode()
        {
            string[] currentFunctionLines = Script.AdditionalFunctionCode.Lines;
            foreach (string line in currentFunctionLines)
            {
                if (line.Trim().StartsWith("//")) continue;
                if (line.Contains("override void Loop()"))
                {
                    return;
                }
            }
            List<string> linesFunctions = new List<string>()
            {
                "",
                "  //全局变量",
                "  Random random = new Random();",
                "  //循环函数，将在后台不断循环",
                "  //开启循环：在Mian函数中调用LoopOn();",
                "  //关闭循环：点击Preview Off按钮或者调用LoopOff();",
                "  public override void Loop()",
                "  {",
                "    //循环案例：",
                "    //创建一个高度随机变化的Box",
                "    BoundingBox box = new BoundingBox(Point3d.Origin, new Point3d(100, 100, random.Next(10,100)));",
                "    ",
                "    //清除上次Show的内容",
                "    ClearShow();",
                "    //显示本次内容",
                "    Show(Mesh.CreateFromBox(box, 1, 1, 1));",
                "    //绘制当前Show到Rhino中",
                "    Redraw();",
                "    //停止100毫秒",
                "    Sleep(100);",
                "  }",
                "",
            };
            linesFunctions.AddRange(currentFunctionLines);
            Script.AdditionalFunctionCode.UpdateLines(linesFunctions);

            string[] currentMainLines = Script.MainCode.Lines;
            foreach (string line in currentMainLines)
            {
                if (line.Trim().StartsWith("//")) continue;
                if (line.Contains("LoopOn();"))
                {
                    return;
                }
            }
            List<string> linesMain = new List<string>(currentMainLines)
            {
                "    ",
                "    //在命令运行时开启循环",
                "    LoopOn();",
                "    ",
            };
            Script.MainCode.UpdateLines(linesMain);
        }

        #endregion

        #region Compile and Run

        public string CurrentCodeText() => Script.GetCodeForScriptEditor();

        #endregion

        #region Open Save Share

        string ReferenceDirectiveChangedHash = "";//First Time sync must trigger this to change, if there is reference directives
        public string[] ExternalReferencesLocations => Script.ReferenceFiles;
        public void SyncModifyToScript(SyntaxConvertResult result, out bool referenceDirectiveChanged)
        {
            Script.SetUsingCode(result.Usings); //需要包含AiMoCore
            Script.MainCode.UpdateLines(result.Mains); // 可能为空
            Script.AdditionalFunctionCode.UpdateLines(result.Functions);
            Script.AdditionalClassCode.UpdateLines(result.Classes);// 需要检查位置
            Script.SetReferenceFiles(result.ReferenceDirectives);
            Script.UpdateScriptMode(result.CompileMode, out bool modeChanged);
            if (modeChanged)
            {
                CompileModeChanged?.Invoke(result.CompileMode);
            }
            string referenceDirectiveChangedHash = Script.GetReferenceExistedChangedHash();
            referenceDirectiveChanged = ReferenceDirectiveChangedHash != referenceDirectiveChangedHash;
            ReferenceDirectiveChangedHash = referenceDirectiveChangedHash;
            // 附属的信息，用来传递
            Script.GH_ParamsInfos = result.GH_ParameterInfo;
            Script.HasPublicCustomizedClasses = result.HasPublicCustomizedClasses;
        }


        public bool PerformOpen(out string error)
        {
            // 实现打开脚本的逻辑
            ExternalSettings.Load();
            string initialDirectory = ExternalSettings.ScriptsFolderWithHistory;
            CodeIO.RepairExtensions(initialDirectory);
            OpenFileDialog dialog = new OpenFileDialog()
            {
                InitialDirectory = initialDirectory,
                RestoreDirectory = true,
                Filter = $"AiMo Script File|*{CodeIO.Extension};*.AimoScipts",
                Title = Local.Get("Open AiMo Script"),
                DefaultExt = CodeIO.Extension,
                CheckFileExists = true,
                Multiselect = false,
            };

            if (dialog.ShowDialog() == true)
            {
                string filePath = dialog.FileName;
                string directory = Path.GetDirectoryName(filePath) ?? "";

                if (directory != initialDirectory)
                {
                    CodeIO.RepairExtensions(directory, ref filePath);
                    ExternalSettings.ScriptsFolderWithHistory = directory;
                    ExternalSettings.Save();
                }
                return AiMoProgram.Instance.OpenNewAimoScriptForm(filePath, out error);
            }
            else
            {
                error = Local.Get("Cancel");
                return false;
            }
        }
        public ShareSaveResult ShowShareSaveInfo(SaveMode saveMode)
        {
            string shareKey = (saveMode == SaveMode.Share) ?
                ActionsUtils.CreateShareKey(ServerHelper.UserName) : "";
            ShareSaveResult saveResult = new ShareSaveResult(
                SavePath, Script.Attributes.Author,
                Script.Attributes.Description,
                Script.Attributes.CodeName,
                shareKey);

            bool showWindow;
            switch (saveMode)
            {
                default:
                    // 一定会显示
                    // Share & Save As
                    showWindow = true;
                    break;
                case SaveMode.Save:
                    // Save时，空路径才显示Window，否则直接保存
                    showWindow = string.IsNullOrEmpty(SavePath);
                    break;
            }
            if (showWindow)
            {
                SaveCodeWindow saveCodeWindow = new SaveCodeWindow(saveMode, saveResult);
                saveCodeWindow.ShowDialog();
                Script.Attributes.Author = saveResult.Author;
                Script.Attributes.Description = saveResult.Description;
            }
            else
            {
                saveResult.OK = true;
            }
            return saveResult;
        }

        public bool PerformSave(ShareSaveResult saveResult, out string printInfo)
        {
            printInfo = "";
            try
            {
                var image = AiMoProgram.Instance.TakeScreenShot;
                if (image != null) Script.Attributes.ScreenShot = image;
            }
            catch
            {
                printInfo += "Cannot get saving screenShot." + Environment.NewLine;
            }
            Script.Attributes.Author = saveResult.Author;
            Script.Attributes.Description = saveResult.Description;
            Script.Attributes.CodeName = saveResult.CodeName;

            if (string.IsNullOrWhiteSpace(saveResult.SavePath))
            {
                printInfo = "Cannto save with savePath empty";
                return false;
            }

            if (saveResult.OK)
            {
                if (SaveToFile(saveResult.SavePath, out string error))
                {
                    printInfo += Local.Get("Saved code to file:") + Environment.NewLine + saveResult.SavePath;
                    SavePath = saveResult.SavePath;
                    return true;
                }
                else
                {
                    printInfo += Local.Get("Save Error:") + Environment.NewLine + error;
                }
            }
            return false;
        }
        bool SaveToFile(string path, out string error)
        {
            try
            {
                SavePath = path;
                FileInfo file = new FileInfo(SavePath);
                if (file.Directory == null) throw new Exception("Cannot save file without directory " + path);
                if (!file.Directory.Exists) file.Directory.Create();
                byte[] bytes = CodeIO.GetBytes(Script, true);
                File.WriteAllBytes(SavePath, bytes);
                SetSaveCodeHash();
                error = "";
                return true;
            }
            catch (Exception e)
            {
                error = e.Message;
                return false;
            }
        }
        /// <summary>
        /// Description 和 Authors 更新并不会被同步
        /// </summary>
        public void CheckNotSave(Window owner, out bool cancel, out string printInfo)
        {
            printInfo = "";
            cancel = false;
            if (NotSave)
            {
                switch (MessageBox.Show(owner, Local.Get("Your code not saved. Save before continue?"), Local.Get("Warning"), MessageBoxButton.YesNoCancel))
                {
                    case MessageBoxResult.Yes:
                        cancel = !PerformSave(ShowShareSaveInfo(SaveMode.Save), out printInfo);
                        break;
                    case MessageBoxResult.Cancel:
                        printInfo = Local.Get("Cancel");
                        cancel = true;
                        return;
                    case MessageBoxResult.No:
                    default:
                        return;
                }
            }
        }


        public bool ShareCodes(IDebugPrinter printer, ShareSaveResult shareResult, out string error)
        {
            error = "";
            Script.Attributes.Author = shareResult.Author;
            Script.Attributes.Description = shareResult.Description;
            Script.Attributes.CodeName = shareResult.CodeName;

            try
            {
                if (!ServerHelper.IsValidShareKey(shareResult.ShareKey))
                {
                    error = "Share Key Invalid";
                    return false;
                }
                byte[] bytes = CodeIO.GetBytes(Script, false);
                IUploadShareCodeClient shareCodeClient = GetClient.GetUploadShareCodeClient();
                bool re = shareCodeClient.Upload(bytes, shareResult.ShareKey, out string info);
                printer.PrintDebug(info);
                return re;
            }
            catch (Exception e)
            {
                error = e.Message;
            }
            return false;
        }
        #endregion

        #region Compile


        readonly RhinoScriptsCompiler compiler = new RhinoScriptsCompiler();
        public MetadataReference[] AllReferences => compiler.GetAllReference(Script);
        string CurrentAssemblyPath { get; set; } = "";
        public void ExcutingCommand(IDebugPrinter printer)
        {
            ExecuterMode executerMode = AiMoProgram.Instance.AiMoExecuterMode;
            if (!Utils.CheckScriptCanSendToExecutor(CurrentAssemblyPath, RhinoExecutor, executerMode, Script.ScriptsMode, printer))
            {
                return;
            }

            if (Script.ScriptsMode == ScriptComplileMode.ExternalAssemblies)
            {
                // 没有Rhino执行器，直接输出Dll
                SaveFileDialog saveFileDialog = new SaveFileDialog()
                {
                    FileName = HasSavePath
                    ? (Path.GetFileNameWithoutExtension(SavePath) + ".dll")
                    : ($"Assembly_Export_{DateTime.Now:MMdd}_" + Guid.NewGuid().ToString()[..3] + ".dll"),
                    DefaultExt = ".dll",
                    Filter = Local.Get("Dll assemblies (*.dll)|*.dll|All (*.*)|*.*"),
                    RestoreDirectory = true,
                    InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
                };
                if (saveFileDialog.ShowDialog() == true)
                {
                    string path = saveFileDialog.FileName;
                    try
                    {
                        File.Copy(CurrentAssemblyPath, path, true);
                    }
                    catch (Exception ex)
                    {
                        printer.PrintDebug(Local.Get("Cannot save assembly file to path:") + $"\r\n{path}:\r\n{ex.Message}");
                        return;
                    }
                    ActionsUtils.SelectFileInWindows(path);
                }
                else
                {
                    printer.PrintDebug(Local.Get("Cancel"));
                }
            }
            else // if(RhinoExecutor!=null) // CheckScriptCanSendToExecutor 已经检查过 RhinoExecutor!
            {

                // 编译，且有Rhino执行器
                bool success = RhinoExecutor!.RunSriptCommand(new ExecutorInfo(CurrentAssemblyPath, printer, Script.ReferenceFiles));
                printer.PrintDebug(RhinoExecutor.RunCommandError);
                printer.PrintDebug(RhinoExecutor.RunCommandTime);

                if (executerMode == ExecuterMode.Grasshopper)
                {
                    SetSaveCodeHash();
                    printer.PrintDebug("Script is sent to GH and saved in grasshopper component");
                }
            }
        }

        public bool Compile(out string error, out string compileTime)
        {
            compileTime = "";
            StringBuilder errorStrings = new StringBuilder();
            if (Script.IsEmpty)
            {
                error = Local.Get("Empty scripts");
                return false;
            }
            Stopwatch timer = Stopwatch.StartNew();
            var result = compiler.CompileScriptInstance(Script, errorStrings, out bool duplicate);
            CurrentAssemblyPath = result.DllPath;
            timer.Stop();
            if (!duplicate)
            {
                compileTime = string.Format(Local.Get("Compiled in {0:f3} sec"), timer.Elapsed.TotalSeconds);
            }
            error = errorStrings.ToString();
            return !string.IsNullOrEmpty(CurrentAssemblyPath);
        }
        #endregion

    }

}
